import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:flutter_scaffold_plugin/pages/vidu/json_constants.dart';
import 'package:flutter_scaffold_plugin/pages/vidu/peers_manager.dart';
import 'package:flutter_scaffold_plugin/pages/vidu/remote_participant.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'dart:io';

class WebSocketListener {
  // ignore: close_sinks
  WebSocket socket;
  final String jsonReversion;
  final bool useSSL;
  int id = 0;
  final String url;
  final String sessionName;
  final String participantName;
  Map<String, String> localOfferParams;
  List<Map<String, String>> iceCandidatesParams = [];
  String userId;
  String remoteParticipantId;
  Map<String, RemoteParticipant> participants = {};
  RTCPeerConnection localPeer;
  final PeersManager peersManager;
  final String token;

  WebSocketListener({
    this.jsonReversion = "2.0",
    this.useSSL = true,
    this.url,
    this.sessionName,
    this.participantName,
    this.peersManager,
    this.token,
  });

  /// listener
  connect() async {
    this.socket = await WebSocket.connect(this.url);
    this.socket.listen(
          this.onReceiveData,
          onDone: this.onConnected,
          onError: this.onError,
        );
  }

  onReceiveData(dynamic data) {
    final text = data as String;
    log("收到信息" + text);
    try {
      final json = JsonDecoder().convert(text) as Map<String, dynamic>;
      if (json[JsonConstants.result] != null) {
        handleResult(json);
      } else {
        handleMethod(json);
      }
    } catch (e) {}
  }

  onConnected() {
    log("连接成功");
    pingMessageHandler();
    joinRoom();
  }

  onError(error) {
    log("失去连接" + error.toString());
  }

  /// actions
  pingMessageHandler() {
    Timer.periodic(Duration(seconds: 5), (timer) {
      final pingParams = {"interval": "5000"};
      sendJson("ping", pingParams);
      this.socket.add(<int>[]);
    });
  }

  joinRoom() {
    Map<String, String> joinRoomParams = {};
    joinRoomParams["recorder"] = "false";

    joinRoomParams["platform"] = "iOS";
    joinRoomParams[JsonConstants.metadata] =
        "{\"clientData\": \"" + "iOSUser" + "\"}";
    joinRoomParams["secret"] = "MY_SECRET";
    joinRoomParams["session"] = this.sessionName;
    joinRoomParams["token"] = this.token;
    sendJson("joinRoom", joinRoomParams);
    if (localOfferParams != null) {
      sendJson("publishVideo", localOfferParams);
    }
  }

  sendJson(String method, Map<String, dynamic> params) {
    final json = Map<String, dynamic>();
    json[JsonConstants.method] = method;
    json[JsonConstants.id] = id;
    this.id += 1;
    json[JsonConstants.params] = params;
    json[JsonConstants.jsonRPC] = this.jsonReversion;

    final jsonString = JsonEncoder().convert(json);
    try {
      this.socket.add(jsonString);
    } catch (e) {
      log(e);
    }
  }

  handleResult(Map<String, dynamic> json) {
    final result = json[JsonConstants.result] as Map<String, dynamic>;
    if (result[JsonConstants.sdpAnswer] != null) {
      saveAnswer(result);
    } else if (result[JsonConstants.sessionId] != null) {
      if (result[JsonConstants.value] != null) {
        final value = result[JsonConstants.value] as List<Map<String, dynamic>>;
        if (value.isEmpty == false) {
          addParticipantsAlreadyInRoom(result);
        }
        this.userId = result[JsonConstants.id] as String;
        for (Map iceCandidate in iceCandidatesParams) {
          iceCandidate["endpointName"] = this.userId;
          sendJson("onIceCandidate", iceCandidate);
        }
      }
    } else if (result[JsonConstants.value] != null) {
      log("pong");
    } else {
      log("Unrecognized");
    }
  }

  addParticipantsAlreadyInRoom(Map<String, dynamic> result) async {
    final values = result[JsonConstants.value] as List<Map<String, dynamic>>;
    for (Map<String, dynamic> participant in values) {
      print(participant[JsonConstants.id]);
      this.remoteParticipantId = participant[JsonConstants.id] as String;
      final metadataString = participant[JsonConstants.metadata] as String;
      try {
        final metadata = JsonDecoder().convert(metadataString);
        final ptName = metadata["clientData"] as String;
        var remoteParticipant = RemoteParticipant(
            id: participant[JsonConstants.id] as String,
            participantName: ptName);

        this.participants[remoteParticipant.id] = remoteParticipant;
        this.peersManager.createRemotePeerConnection(remoteParticipant);
        final mandatoryConstraints = {
          "OfferToReceiveAudio": "true",
          "OfferToReceiveVideo": "true"
        };
        RTCSessionDescription description = await remoteParticipant
            .peerConnection
            .createOffer(mandatoryConstraints);
        log("Remote Offer: " + description.toString());
        await this
            .participants[remoteParticipant.id]
            .peerConnection
            .setLocalDescription(description);
        Map<String, String> remoteOfferParams = {};
        remoteOfferParams["sdpOffer"] = description.sdp;
        remoteOfferParams["sender"] = this.remoteParticipantId + "_CAMERA";
        this.sendJson("receiveVideoFrom", remoteOfferParams);
        this.setPeerConnectDelegate();
      } catch (e) {
        log(e.toString());
      }
    }
  }

  setPeerConnectDelegate() {
    this.peersManager.remotePeer.onSignalingState = (state) {
      this.peersManager.onSignalingState(state, false);
    };
    this.peersManager.remotePeer.onIceGatheringState = (state) {
      this.peersManager.onIceGatheringState(state, false);
    };
    this.peersManager.remotePeer.onIceConnectionState = (state) {
      this.peersManager.onIceConnectionState(state, false);
    };
    this.peersManager.remotePeer.onConnectionState = (state) {
      this.peersManager.onPeerConnectionState(state, false);
    };
    this.peersManager.remotePeer.onIceCandidate = (candidate) {
      this.peersManager.onCandidate(candidate, false);
    };
    this.peersManager.remotePeer.onRenegotiationNeeded = () {
      this.peersManager.onRenegotiationNeeded(false);
    };

    this.peersManager.remotePeer.onAddStream = (stream) {
      this.peersManager.onAddStream(stream, false);
    };
    this.peersManager.remotePeer.onRemoveStream = (stream) {
      this.peersManager.onRemoveStream(stream, false);
    };
  }

  saveAnswer(Map<String, dynamic> json) async {
    final sessionDescription = RTCSessionDescription(
        json[JsonConstants.sdpAnswer] as String, "answer");
    if (localPeer == null) {
      this.localPeer = this.peersManager.localPeer;
    }
    if (localPeer.getRemoteDescription() != null) {
      try {
        await participants[this.remoteParticipantId]
            .peerConnection
            .setRemoteDescription(sessionDescription);
        print("Remote Peer Remote Description set: " +
            sessionDescription.toString());
        if (this.peersManager.remoteStreams.length >=
            this.participants.length) {
          // DispatchQueue.main.async {
          //     print("Count: " + self.participants.count.description)
          //     #if arch(arm64)
          //         let renderer = RTCMTLVideoView(frame:  self.views[self.participants.count-1].frame)
          //     #else
          //         let renderer = RTCEAGLVideoView(frame:  self.views[self.participants.count-1].frame)
          //     #endif
          //     let videoTrack = self.peersManager.remoteStreams[self.participants.count-1].videoTracks[0]
          //     videoTrack.add(renderer)
          //     // Add the view and name to the first free space available
          //     var index = 0
          //     while (index < 2 && !(self.names[index].text?.isEmpty)!) {
          //         index += 1
          //     }
          //     if index < 2 {
          //         self.names[index].text = self.participants[self.remoteParticipantId!]?.participantName
          //         self.names[index].backgroundColor = UIColor.black
          //         self.names[index].textColor = UIColor.white
          //         self.embedView(renderer, into: self.views[index])
          //         self.participants[self.remoteParticipantId!]?.index = index
          //         self.views[index].bringSubview(toFront: self.names[index])
          //     }
          // }
        }
      } catch (e) {
        print("Remote Peer Remote Description set: " + e.toString());
      }
    } else {
      try {
        await localPeer.setRemoteDescription(sessionDescription);
        print("Local Peer Remote Description set: " +
            sessionDescription.toString());
      } catch (e) {
        print("Local Peer Remote Description set: " + e.toString());
      }
    }
  }

  handleMethod(Map<String, dynamic> json) {
    if (json[JsonConstants.params] != null) {
      final method = json[JsonConstants.method] as String;
      final params = json[JsonConstants.params] as Map<String, dynamic>;
      switch (method) {
        case JsonConstants.iceCandidate:
          iceCandidateMethod(params);
          break;

        case JsonConstants.participantJoined:
          participantJoinedMethod(params);
          break;

        case JsonConstants.participantPublished:
          participantPublished(params);
          break;

        case JsonConstants.participantLeft:
          participantLeft(params);
          break;
        default:
          print("Error handleMethod, " +
              "method '" +
              method +
              "' is not implemented");
      }
    }
  }

  iceCandidateMethod(Map<String, dynamic> params) {
    if (params["endpointName"] as String == userId) {
      saveIceCandidate(params, null);
    } else {
      saveIceCandidate(params, params["endpointName"] as String);
    }
  }

  participantJoinedMethod(Map<String, dynamic> params) {
    final metadataString = params[JsonConstants.metadata] as String;
    try {
      final metadata = JsonDecoder().convert(metadataString);
      final ptName = metadata["clientData"] as String;
      var remoteParticipant = RemoteParticipant(
          id: params[JsonConstants.id] as String, participantName: ptName);
      this.participants[params[JsonConstants.id] as String] = remoteParticipant;
      this.peersManager.createRemotePeerConnection(remoteParticipant);
    } catch (e) {
      log(e.toString());
    }
  }

  participantPublished(Map<String, dynamic> params) async {
    this.remoteParticipantId = params[JsonConstants.id] as String;
    log("ID: " + remoteParticipantId);
    final remoteParticipantPublished = participants[remoteParticipantId];
    final mandatoryConstraints = {
      "OfferToReceiveAudio": "true",
      "OfferToReceiveVideo": "true"
    };
    RTCSessionDescription sessionDescription = await remoteParticipantPublished
        .peerConnection
        .createOffer(mandatoryConstraints);
    await remoteParticipantPublished.peerConnection
        .setLocalDescription(sessionDescription);
    print("Remote Peer Local Description set");
    Map<String, String> remoteOfferParams = {};
    remoteOfferParams["sdpOffer"] = sessionDescription.sdp;
    remoteOfferParams["sender"] = remoteParticipantPublished.id + "_webcam";
    this.sendJson("receiveVideoFrom", remoteOfferParams);
    setPeerConnectDelegate();
  }

  participantLeft(Map<String, dynamic> params) {
    log("participants:" + this.participants.toString());
    log("params:" + params.toString());
    final participantId = params["connectionId"] as String;
    participants[participantId].peerConnection.close();

    /// TODO: delete
    //REMOVE VIEW
    // #if arch(arm64)
    //     let renderer = RTCMTLVideoView(frame:  self.views[0].frame)
    // #else
    //     let renderer = RTCEAGLVideoView(frame:  self.views[self.participants.count-1].frame)
    // #endif

    // let videoTrack = self.peersManager.remoteStreams[0].videoTracks[0]
    // videoTrack.remove(renderer)
    // if let index = self.participants.keys.index(of: participantId) {
    // 	let i = participants.distance(from: participants.startIndex, to: index)
    // 	self.views[i].willRemoveSubview(renderer)
    // 	self.names[i].text = ""
    // 	self.names[i].backgroundColor = UIColor.clear
    // }
    this.participants.remove(participantId);
  }

  saveIceCandidate(Map<String, dynamic> json, String endPointName) {
    final iceCandidate = RTCIceCandidate(json["candidate"] as String,
        json["sdpMid"] as String, json["sdpMLineIndex"] as int);
    if (endPointName == null || participants[endPointName] == null) {
      this.localPeer = this.peersManager.localPeer;
      this.localPeer.addCandidate(iceCandidate);
    } else {
      participants[endPointName].peerConnection.addCandidate(iceCandidate);
    }
  }

  addIceCandidate(Map<String, String> iceCandidateParams) {
    iceCandidatesParams.add(iceCandidateParams);
  }
}
